package net.linku.annotation.config;

import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.expression.AnnotatedElementKey;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.context.expression.CachedExpressionEvaluator;
import org.springframework.context.expression.MethodBasedEvaluationContext;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.lang.Nullable;

import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 用于评估 Spring 表达式语言（SpEL）表达式的工具类。
 * 该类提供了创建评估上下文和评估表达式的方法，并支持缓存机制以提高性能。
 * 它与 Spring 的 bean 工厂和上下文集成，以解析 bean 和参数。
 * <p>
 * 该实现包括对表达式和方法的缓存，以改善性能。
 * </p>
 * <p>
 * 使用示例：
 * <pre>
 * LinkUExpressionEvaluator evaluator = new LinkUExpressionEvaluator();
 * EvaluationContext context = evaluator.createContext(method, args, target, targetClass, beanFactory);
 * String result = evaluator.evalAsText(expression, methodKey, context);
 * </pre>
 * </p>
 */
public class LinkUExpressionEvaluator extends CachedExpressionEvaluator {

    private final Map<ExpressionKey, Expression> expressionCache = new ConcurrentHashMap<>(64);
    private final Map<AnnotatedElementKey, Method> methodCache = new ConcurrentHashMap<>(64);

    /**
     * 为给定方法调用创建一个评估上下文，包含方法参数和目标对象。
     *
     * @param method       被评估的方法
     * @param args         方法的参数
     * @param target       方法的目标对象
     * @param targetClass  目标对象的类
     * @param beanFactory  Spring bean 工厂（可以为 null）
     * @return 为方法调用创建的 {@link EvaluationContext}
     */
    public EvaluationContext createContext(Method method, Object[] args, Object target, Class<?> targetClass, @Nullable BeanFactory beanFactory) {
        Method targetMethod = this.getTargetMethod(targetClass, method);
        LinkUExpressionRootObject rootObject = new LinkUExpressionRootObject(method, args, target, targetClass, targetMethod);
        MethodBasedEvaluationContext evaluationContext = new MethodBasedEvaluationContext(rootObject, targetMethod, args, this.getParameterNameDiscoverer());
        if (beanFactory != null) {
            evaluationContext.setBeanResolver(new BeanFactoryResolver(beanFactory));
        }
        return evaluationContext;
    }

    /**
     * 为给定方法调用创建一个评估上下文，使用自定义根对象。
     *
     * @param method       被评估的方法
     * @param args         方法的参数
     * @param targetClass  目标对象的类
     * @param rootObject   自定义的根对象
     * @param beanFactory  Spring bean 工厂（可以为 null）
     * @return 为方法调用创建的 {@link EvaluationContext}
     */
    public EvaluationContext createContext(Method method, Object[] args, Class<?> targetClass, Object rootObject, @Nullable BeanFactory beanFactory) {
        Method targetMethod = this.getTargetMethod(targetClass, method);
        MethodBasedEvaluationContext evaluationContext = new MethodBasedEvaluationContext(rootObject, targetMethod, args, this.getParameterNameDiscoverer());
        if (beanFactory != null) {
            evaluationContext.setBeanResolver(new BeanFactoryResolver(beanFactory));
        }
        return evaluationContext;
    }

    /**
     * 根据给定的表达式、方法键和评估上下文，计算表达式的结果。
     *
     * @param expression  要计算的表达式
     * @param methodKey   方法的键
     * @param evalContext 评估上下文
     * @return 表达式的计算结果
     */
    @Nullable
    public Object eval(String expression, AnnotatedElementKey methodKey, EvaluationContext evalContext) {
        return this.eval(expression, methodKey, evalContext, (Class<?>) null);
    }

    /**
     * 根据给定的表达式、方法键和评估上下文，计算表达式的结果，并将结果转换为指定类型。
     *
     * @param expression  要计算的表达式
     * @param methodKey   方法的键
     * @param evalContext 评估上下文
     * @param valueType   结果的期望类型
     * @param <T>         结果的类型
     * @return 表达式的计算结果，转换为指定类型
     */
    @Nullable
    public <T> T eval(String expression, AnnotatedElementKey methodKey, EvaluationContext evalContext, @Nullable Class<T> valueType) {
        return this.getExpression(this.expressionCache, methodKey, expression).getValue(evalContext, valueType);
    }

    /**
     * 根据给定的表达式、方法键和评估上下文，计算表达式的结果，并将结果转换为字符串类型。
     *
     * @param expression  要计算的表达式
     * @param methodKey   方法的键
     * @param evalContext 评估上下文
     * @return 表达式的计算结果，转换为字符串
     */
    @Nullable
    public String evalAsText(String expression, AnnotatedElementKey methodKey, EvaluationContext evalContext) {
        return (String) this.eval(expression, methodKey, evalContext, String.class);
    }

    /**
     * 根据给定的表达式、方法键和评估上下文，计算表达式的结果，并将结果转换为布尔类型。
     *
     * @param expression  要计算的表达式
     * @param methodKey   方法的键
     * @param evalContext 评估上下文
     * @return 表达式的计算结果，转换为布尔值
     */
    public boolean evalAsBool(String expression, AnnotatedElementKey methodKey, EvaluationContext evalContext) {
        return Boolean.TRUE.equals(this.eval(expression, methodKey, evalContext, Boolean.class));
    }

    /**
     * 根据目标类和方法获取最具体的方法。
     *
     * @param targetClass  目标类
     * @param method       方法
     * @return 最具体的方法
     */
    private Method getTargetMethod(Class<?> targetClass, Method method) {
        AnnotatedElementKey methodKey = new AnnotatedElementKey(method, targetClass);
        return this.methodCache.computeIfAbsent(methodKey, key -> AopUtils.getMostSpecificMethod(method, targetClass));
    }

    /**
     * 清除缓存的表达式和方法。
     */
    public void clear() {
        this.expressionCache.clear();
        this.methodCache.clear();
    }
}
